package de.bazinga.rotw.player;

import java.util.Vector;

import com.jme.input.KeyBindingManager;
import com.jme.input.MouseInput;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial;
import com.jmex.game.state.GameStateManager;

import de.bazinga.rotw.collision.CollidableObjects;
import de.bazinga.rotw.collision.CollisionHandler;
import de.bazinga.rotw.effects.Nightvision;
import de.bazinga.rotw.game.Game;
import de.bazinga.rotw.game.RotwGameState;
import de.bazinga.rotw.game.TextCache;
import de.bazinga.rotw.network.BasicSyncManager;
import de.bazinga.rotw.network.Client;
import de.bazinga.rotw.network.Server;
import de.bazinga.rotw.network.message.ChatMessage;
import de.bazinga.rotw.network.message.FallDamageMessage;
import de.bazinga.rotw.network.message.FallDiedMessage;
import de.bazinga.rotw.objects.GameObject;
import de.bazinga.rotw.sound.Sound;
import de.bazinga.rotw.statistics.OnlineStatistics;
import de.bazinga.rotw.util.GlobalSettings;
import de.bazinga.rotw.vehicle.Car;
import de.bazinga.rotw.vehicle.Vehicle;
import de.bazinga.rotw.weapon.portal.EntryPortal;
import de.bazinga.rotw.weapon.portal.PortalList;

public class PlayerActions extends KeyInputAction {
	private Player player = null;

	private Vector3f direction;
	
	private boolean falling = false;
	private float fallingStartY = 0;
	
	private boolean ducking = false;
	
	private long lastMovement = 0;
	private long lastStepSound = 0;
	
	private long lastWeaponChange = 0;
	
	private boolean chatting = false;
	
	
	private CollisionHandler collHandler = null;
	
	/**
	 * Is the player on the Terrain or not?
	 */
	private boolean onTerrain = false;
	
	/**
	 * Is the player in the Water?
	 */
	
	private boolean inWater = false;
	
	
	/**
	 * The last time he reloaded
	 */
	private long reloadTime = 0;
	private long reloadLag = 2500;
	
	

	private Vector3f movingForward = null;
	private Vector3f leftRight = null;
	
	/**
	 * Saves the last location so we can calculate the distance
	 */
	private Vector3f lastLocation = null;
	
	
	private Vector3f lastValidLocation = new Vector3f();
	
	public PlayerActions(Player player) {
		this.player = player;
		
		player.setPlayerActions(this);
		
		collHandler = new CollisionHandler(player);

		direction = Game.getGame().getCamera().getDirection();
		direction.normalizeLocal();
	}
	
	
	public void performAction(InputActionEvent evt) {
		/**
		 * CHAT-STUFF!
		 */
		if(chatting) {
			handleChat(evt);			
			return ;
		}
		
		// Start typing a Chat-Message?
		if(evt.getTriggerName().equals("talk") || evt.getTriggerName().equals("T")) {
			chatting = true;
			TextCache.getInstance().getTextfield("chatMsg").setText(">");
			
			return ;
		}
		
		
		// If the Player is dead, he can't do anything except chatting
		if(player.isDead()) return ;
		
		
		
		if(evt.getTriggerName().equals("L")) {
			System.out.println("Current Location: "+player.getLocalTranslation());
		}
		
		
		
		// Next Weapon
		if(!isReloading()) {
			if(evt.getTriggerName().equals("N")) {
				player.nextWeapon();
				reloadLag = player.getCurrentWeapon().getReloadLag();
			}
			// Change Weapon by number
			for(int i=1;i<=9;i++) {
				if(evt.getTriggerName().equals(String.valueOf(i))) {
					player.setWeapon(i-1);
					reloadLag = player.getCurrentWeapon().getReloadLag();
					break;
				}
			}
		}
		
		
		// Reload?
		if(evt.getTriggerName().equals("R") && 
				player.getCurrentWeapon().canReload() && 
				(System.currentTimeMillis() - reloadTime) > reloadLag) {
			
			if(player.getCurrentWeapon().reload()) {
				// Only do this stuff if he has magazines and can reload
				reloadTime = System.currentTimeMillis();
				
				player.updateAmmoText();
				
				if(Game.getGame().getSettings().isSFX()) {
					Sound.getInstance().getAudioTrack("sounds/reload.wav").play();
				}
			}
		}
		
		

		
		
		// ENTER CAR?!
		if(evt.getTriggerName().equals("E") && !player.hasZombie()) {
			if(player.hasVehicle() && !player.getVehicle().isDriving()) {
				// Exit Car				
				player.getVehicle().setLocalTranslation(
						player.getVehicle().getLocalTranslation().clone());
				player.setLocalTranslation(player.getVehicle().getExitLocation());
				
				// Reset Valid-Pos so the height-check doesnt fail
				player.updateGeometricState(0, false);
				if(collHandler.collisionWithWall() || collHandler.collisionWithObject()) {
					player.setLocalTranslation(
							player.getVehicle().getLocalTranslation());
				}else{
					player.getVehicle().setCurrentlyUsed(false);
					player.setVehicle(null);
				}
			}else{
				// Enter Car?
				Vector<Spatial> cars = CollidableObjects.getInstance().getObjects("vehicle");
				if(cars != null) {
					for(int i=0;i < cars.size(); i++) {
						if( !((Vehicle)cars.get(i)).isCurrentlyUsed() && cars.get(i).getLocalTranslation().distance(player.getLocalTranslation()) < 3.2f) {
							//player.setLocalTranslation(((Node)cars.get(i)).getLocalTranslation());
							player.setVehicle((Vehicle)cars.get(i));
							
							((Vehicle)cars.get(i)).setCurrentlyUsed(true);
							
							Sound.getInstance().getAudioTrack("sounds/engineStart.wav").play();
							break;
						}
					}
				}
			}
		}
		
		
		
		if(player.hasVehicle()) {
			// Let the Vehicle do the Handling!
			player.getVehicle().handleEvent(evt);
			
			return ;
		}
		
		
		
		
		//boolean hasMoved = false;
		if(evt.getTriggerName().equals("SPACE")) {
			jump();
		}else if(evt.getTriggerName().equals("P")) {
			Nightvision.getInstance().switchEnabled();
		}
    }
	
	private boolean moveForBackward(float tpf,float speed) {		
		if(KeyBindingManager.getKeyBindingManager().isValidCommand("moveUp",true) ||
				KeyBindingManager.getKeyBindingManager().isValidCommand("moveDown",true)) {
			if(movingForward != null)
	    		player.getWalkingDirection().subtractLocal(movingForward);
	
	    	if(KeyBindingManager.getKeyBindingManager().isValidCommand("moveDown",true))
				movingForward = new Vector3f(direction.x,0.0f,direction.z).normalize().mult(-tpf*speed);
	    	else
				movingForward = new Vector3f(direction.x,0.0f,direction.z).normalize().mult(tpf*speed);
	    	
			player.getWalkingDirection().addLocal(movingForward);
			return true;
		}else if(movingForward != null){
			player.getWalkingDirection().subtractLocal(movingForward);
			movingForward = null;
		}
		return false;
	}
	
	private boolean moveLeftRight(float tpf,float speed) {	
		if(KeyBindingManager.getKeyBindingManager().isValidCommand("moveLeft",true) ||
				KeyBindingManager.getKeyBindingManager().isValidCommand("moveRight",true)) {
			if(leftRight != null)
	    		player.getWalkingDirection().subtractLocal(leftRight);
	
	    	if(KeyBindingManager.getKeyBindingManager().isValidCommand("moveLeft",true))
	    		leftRight = new Vector3f(direction.x,0.0f,direction.z).normalize().cross(
									new Vector3f(0,1.0f,0f)).mult(-tpf*speed);
	    	else
	    		leftRight = new Vector3f(direction.x,0.0f,direction.z).normalize().cross(
							new Vector3f(0,1.0f,0f)).mult(tpf*speed);
	    	
			player.getWalkingDirection().addLocal(leftRight);
			return true;
		}else if(leftRight != null){
			player.getWalkingDirection().subtractLocal(leftRight);
			leftRight = null;
		}
		return false;
	}
	
	
	
	public void update(float tpf) {
		// Do nothing if the Player is dead
		if(player.isDead()) {
			return ;
		}
		
		// Should we reset?
		if(player.shouldReset()) {
			player.setResetAfterDeath(false);
			player.reset(false);
		}
		
		
		
		//Only do it if the player is not driving a vehicle
		if(!player.hasVehicle() && !chatting) {
			
			// Get Speed
			float speed = GlobalSettings.SPEED;
			if(!player.getPhysicsNode().getCharacterController().onGround())
				speed = GlobalSettings.AIRSPEED;
			
			boolean sneakWalking = false;
			if(KeyBindingManager.getKeyBindingManager().isValidCommand("sneak")) {
				sneakWalking = true;
				speed = GlobalSettings.SNEAKSPEED;
			}
			
			if(ducking) speed = GlobalSettings.DUCKSPEED;
			
			if(inWater) 
				speed = GlobalSettings.SWIMSPEED;
			
			// Decrease by weapon
			if(player.getCurrentWeapon() != null) 
				speed *= player.getCurrentWeapon().getSpeedFactor();
		
			
			boolean isMoving = false;
			
			// Forward/Backward
			isMoving = moveForBackward(tpf,speed);
			
			
			// Left/Right
			boolean isMovingLeftRight = moveLeftRight(tpf, speed);
			if(!isMoving) isMoving = isMovingLeftRight;
			
			
			// Apply Moving
			player.getPhysicsNode().setWalkDirection(player.getWalkingDirection());
			
			
			if(isMoving) {
				// Save the last movement so we know if the player stands or is walking
				lastMovement = System.currentTimeMillis();
				
				// Let the Gamemode do some stuff
				((RotwGameState)GameStateManager.getInstance().getChild("gameState"))
					.getGameMode().playerAction();
				
				
				// play animation
				if(sneakWalking) player.getAnimation().sneakWalking();
				else player.getAnimation().walk();
				
				// Play Step-Sound!
				if(Game.getGame().getSettings().isSFX() && 
						(player.getPhysicsNode().getCharacterController().onGround() ||
						isInWater())) {
					int lagDuration = 250;
					
					if(player.getAnimation().isDucking()) lagDuration = 400;
					else if(player.getAnimation().isSneakWalking()) lagDuration = 300;
					else if(inWater) lagDuration = 500;
					
					if( (System.currentTimeMillis() - lastStepSound) > lagDuration) {
						if(inWater) Sound.getInstance().getAudioTrack("sounds/swimming.wav").play();
						else Sound.getInstance().getAudioTrack("sounds/steps.wav").play();
						lastStepSound = System.currentTimeMillis();
					}
				}
			}
			
			
			
			/**
			 * Falling?
			 */
			boolean onGround = player.getPhysicsNode().getCharacterController().onGround();
			if(!falling && !onGround) {
				falling = true;
				fallingStartY = player.getPhysicsNode().getLocalTranslation().getY();
			}
			
			/**
			 * Hit the ground
			 */
			if(falling && onGround) {
				float fallHeight = fallingStartY - player.getPhysicsNode().getLocalTranslation().getY();
				falling = false;
				
				if(fallHeight > GlobalSettings.MAXFALLHEIGHTWITHOUTDAMAGE) {
					player.fallDamage(fallHeight);
					
					// Send Message
					if(!((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
						// Is no Server, so tell the Server we fell
						BasicSyncManager syncManager = 
							((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
						FallDamageMessage msg = new FallDamageMessage();
						msg.setDistance(fallHeight);
						((Client)syncManager).getClient().sendToServer(msg);
					}
				}
			}
			
		
			
			
			/**
			 * Is the player in the water?
			 */
			// In Water?
			float waterHeight = 
				((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getWaterHeight(player.getLocalTranslation());
			
			
			if((waterHeight + 1) > player.getLocalTranslation().getY()) {
				//player.getLocalTranslation().setY(waterHeight);
				Vector3f loc = player.getLocalTranslation();
				loc.setY(waterHeight+1);
				player.setLocalTranslation(loc);
				falling = false;
				setInWater(true);
			}else{
				setInWater(false);
			}
			
			
			/**
			 * Under the terrain?
			 */
			//float terrainHeight = collHandler.getTerrainHeight();
			float terrainHeight = collHandler.getTerrainHeight();
			if(!isInWater() &&
					terrainHeight - 0.05f < player.getLocalTranslation().y &&
					terrainHeight + 0.05f > player.getLocalTranslation().y &&
					collHandler.getTerrainHeightDifference(lastValidLocation) > 0.22f) {
				//Vector3f loc = player.getLocalTranslation();
				//loc.setY(terrainHeight+.3f);
				player.setLocalTranslation(lastValidLocation);
			}/*else{
				lastValidLocation = player.getLocalTranslation().clone();
			}*/
			
			if(terrainHeight != Float.NaN && 
					player.getLocalTranslation().getY() < collHandler.getTerrainHeight()) {
				player.setLocalTranslation(lastValidLocation.clone().add(new Vector3f(0,0.3f,0)));
			}else{
				lastValidLocation = player.getLocalTranslation().clone();
			}
			
			
			
			/**
			 * Fell to much?
			 */
			if(falling && player.getLocalTranslation().y < -50) {
				player.diedFalling();
				
				// Reset Vehicle
				if(player.getVehicle() != null) player.getVehicle().reset();
				
				if(!((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
					// Is no Server, so tell the Server we fell
					BasicSyncManager syncManager =
						((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
					((Client)syncManager).getClient().sendToServer(new FallDiedMessage());
				}
			}
		}
		
		
		// Fire?
		if(player.getCurrentWeapon() != null && !isReloading()) {
			player.getCurrentWeapon().updateWeapon(player);
		}
		
		
		
		// Vehicle-Update
		if(player.hasVehicle()) 
			player.getVehicle().update(tpf);
		


		
		// Check for any Collision
		if(!player.hasVehicle()) {
			collisionWithGameObject();
			collisionWithPortal(tpf);
			
			((RotwGameState)GameStateManager.getInstance().getChild("gameState"))
				.getGameMode().playerUpdate();
		}
		
		
		
		
		// Next/Prev Weapon?
		if( ( System.currentTimeMillis() - lastWeaponChange) > 250 && !chatting && player.getCurrentWeapon() != null && !isReloading()) {
			if(MouseInput.get().getWheelDelta() > 0) {
				player.previousWeapon();
				lastWeaponChange = System.currentTimeMillis();
			}else if(MouseInput.get().getWheelDelta() < 0) {
				player.nextWeapon();
				lastWeaponChange = System.currentTimeMillis();
			}
			reloadLag = player.getCurrentWeapon().getReloadLag();
		}
		
		
		
		
		
		
		
		/**
		 * DUCKING?
		 */
		if(!chatting) {
			if(KeyBindingManager.getKeyBindingManager().isValidCommand("duck")) {
				ducking = true;
				
				if(!player.getAnimation().isDucking()) 
					player.getAnimation().duck();
			}else{
				ducking = false;
				
				if(player.getAnimation().isDucking()) 
					player.getAnimation().stand();
			}
		}
		
		
		/**
		 * Show Statistics?
		 */
		if(KeyBindingManager.getKeyBindingManager().isValidCommand("statistic")) {
			if(!GameStateManager.getInstance().getChild("statistics").isActive())
				GameStateManager.getInstance().getChild("statistics").setActive(true);
		}else{
			if(GameStateManager.getInstance().getChild("statistics").isActive())
				GameStateManager.getInstance().getChild("statistics").setActive(false);
		}
		
		
		
		
		// Save the distance we traveled
		lastLocation.setY(0);
		if(player.hasVehicle()) {
			float distance = 0;
			if(player.getVehicle() instanceof Car) {
				Vector3f carLoc = ((Car)player.getVehicle()).getPhysicsCar().getLocalTranslation().clone();
				carLoc.setY(0);
				
				distance = carLoc.distance(lastLocation);
				lastLocation.set(((Car)player.getVehicle()).getPhysicsCar().getLocalTranslation());
			}
			OnlineStatistics.distanceTraveledVehicle += distance;
		}else{
			Vector3f playerLoc = player.getLocalTranslation().clone();
			playerLoc.setY(0);
			
			float distance = lastLocation.distance(playerLoc);
			OnlineStatistics.distanceTraveled += distance;
			lastLocation.set(player.getLocalTranslation());
		}
		
		
		
		
		// Stop walking if he didnt move in 100ms
		if((System.currentTimeMillis() - lastMovement) > 100 && !player.getAnimation().isDucking() && !player.getAnimation().isJumping() && !player.getAnimation().isFiring()) {
			player.getAnimation().stand();
		}
		
		
		
		// Update Camera Location
		Game.getGame().getCamera().setLocation(player.getCameraPosition());
	}
	
	
	
	
	public void jump() {
		jump(GlobalSettings.JUMPYSPEED);
	}
	
	public void jump(float jumpSpeed) {
		player.getPhysicsNode().getCharacterController()
			.setJumpSpeed(jumpSpeed);
		player.getPhysicsNode().jump();
		

		if(Game.getGame().getSettings().isSFX()) {
			Sound.getInstance().getAudioTrack("sounds/pop.wav").setWorldPosition(player.getWorldTranslation());
			Sound.getInstance().getAudioTrack("sounds/pop.wav").play();
		}
	}
	
	
	public void reset() {
		falling = false;
		lastLocation = player.getLocalTranslation().clone();
		lastValidLocation = player.getLocalTranslation().clone();
		fallingStartY = -999;
	}
	
	
	public void collisionWithGameObject() {
		/**
		 * Collision with GameObject?
		 * (e.g. Health/Ammo/Armor)
		 */
		Vector<Spatial> gameObjects = CollidableObjects.getInstance().getObjects("gameObjects");
		if(gameObjects != null) {
			for(Spatial a:gameObjects) {
				if(player.hasCollision(a, false)) {
					((GameObject)a).onCollision(player, true);
				}
			}
		}
	}
	
	public void collisionWithPortal(float tpf) {
		/**
		 * Collision with Portal?
		 */
		if(player.hasZombie()) return ;
		
		for(EntryPortal p:PortalList.getInstance().getPortals()) {
			if(p.getExitPortal() != null) {
				if(player.hasCollision(p, false)) {
					player.setLocalTranslation(
							p.getExitPortal().getDestLocation().clone());
					lastValidLocation = player.getLocalTranslation().clone();
					// Reset Valid-Pos so the height-check doesnt fail
					if(Game.getGame().getSettings().isSFX())
						Sound.getInstance().getAudioTrack("sounds/portalBeam.wav").play();
				}
			}
		}
	}
	
	
	
	
	
	public void setFalling(boolean falling) {
		if(!falling) fallingStartY = player.getLocalTranslation().getY();
		this.falling = falling;
	}
	
	
	
	
	private void handleChat(InputActionEvent evt) {
		// The Player is currently typing a Message
		String appendString = "";
		
		String[] chatInput = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",".",",","?"};
		
		for(String s:chatInput) {
			if(evt.getTriggerName().equals(s)) {
				appendString = s;
			}
		}
		
		if(evt.getTriggerName().equals("SPACE")) {
			appendString = " ";
		}
		
		if(KeyBindingManager.getKeyBindingManager().isValidCommand("shift")) {
			// Set char to uppercase
			appendString = appendString.toUpperCase();
			// Replace some special-chars
			if(appendString.equals(".")) appendString = ":";
			if(appendString.equals(",")) appendString = ";";
			if(appendString.equals("1")) appendString = "!";
			if(appendString.equals("5")) appendString = "%";
			if(appendString.equals("6")) appendString = "&";
			if(appendString.equals("8")) appendString = "(";
			if(appendString.equals("9")) appendString = ")";
			if(appendString.equals("0")) appendString = "=";
		}else{
			appendString = appendString.toLowerCase();
		}
		
		// Append char
		TextCache.getInstance().getTextfield("chatMsg").setText(
				TextCache.getInstance().getTextfield("chatMsg").getText() + appendString);
		

		if(evt.getTriggerName().equals("del")) {
			int currLength = TextCache.getInstance().getTextfield("chatMsg").getText().length();
			if(currLength > 1) {
				TextCache.getInstance().getTextfield("chatMsg").setText(
						TextCache.getInstance().getTextfield("chatMsg").getText().substring(0, currLength-1));
			}
		}
		
		if(evt.getTriggerName().equals("send")) {
			// Only send if the player typed a msg
			if(!TextCache.getInstance().getTextfield("chatMsg").getText().equals(">")) {
				ChatMessage cmsg = new ChatMessage();
				cmsg.setMsg(player.getPlayerName()+": "+TextCache.getInstance().getTextfield("chatMsg").getText().substring(1));
				
				BasicSyncManager syncManager = 
					((RotwGameState)GameStateManager.getInstance().getChild("gameState")).getSyncManager();
				if(((RotwGameState)GameStateManager.getInstance().getChild("gameState")).isServer()) {
					// Server
					((Server)syncManager).getServer().sendToAll(cmsg);
					
					if(cmsg.getMsg().endsWith("FULLDAMAGE")) {
						player.setDamageFactor(100);
					}else if(cmsg.getMsg().endsWith("GODMODE")) {
						player.setGodMode(true);
					}else{
						((RotwGameState)GameStateManager.getInstance().getChild("gameState")).addChatMessage(
								cmsg.getMsg());
					}
				}else{
					if(cmsg.getMsg().endsWith("GODMODE")) {
						player.setGodMode(true);
					}
					((Client) syncManager).getClient().sendToServer(cmsg);
				}
			}
			
			TextCache.getInstance().getTextfield("chatMsg").setText("");
			
			chatting = false;
		}
	}


	public boolean isOnTerrain() {
		return onTerrain;
	}


	public void setOnTerrain(boolean onTerrain) {
		this.onTerrain = onTerrain;
	}
	
	
	public boolean isReloading() {
		if( (System.currentTimeMillis() - reloadTime) > reloadLag) {
			return false;
		}
		return true;
	}


	public boolean isInWater() {
		return inWater;
	}


	public void setInWater(boolean inWater) {
		if(this.inWater != inWater) {
			if(inWater) {
				// Set Rotation
				player.getAnimation().swimming();
			}else{
				// Delete Rotation
				player.getAnimation().stopSwimming();
			}
		}
		
		this.inWater = inWater;
	}
}
